瑞和云图Golang工程师技术题
第一部分:Golang基础
问题1:Goroutine与线程的区别
简述Goroutine与传统线程的区别,并说明Goroutine为什么更轻量。
1、协程通过gmp调度,一个线程对应多个协程,用户态切换开销更少。
2、协程在占用空间小。
3、单机可轻松创建数百万Goroutine,线程数受限于内核参数。
问题2:并发控制
编写代码实现一个并发任务调度器,限制同时运行的Goroutine数量为5个,处理100个耗时任务。(请用伪代码实现)
Channel、sync.WaitGroup同时使用才可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
tasks := make(chan Task, 5) // 缓冲Channel控制并发数 var wg sync.WaitGroup // 启动5个Worker for i := 0; i < 5; i++ { go func() { for task := range tasks { process(task) wg.Done() } }() } // 提交100个任务 for i := 0; i < 100; i++ { wg.Add(1) tasks <- Task{} } wg.Wait() close(tasks) |
问题3:并发安全
分析以下代码是否存在并发安全问题,并提出改进方案:
type Counter struct {
value int
}
func (c *Counter) Increment() {
c.value++
}
func (c *Counter) Value() int {
return c.value
}
存在并发问题。
解决方案:
1,加锁,读写锁更好
2,原子计数
3,把go调成单核--
问题4:Channel高级应用----
实现一个带有超时机制的Channel读取操作,当超过500ms没有数据时返回错误。(请用伪代码实现)
|
|
func readWithTimeout(ch <-chan interface{}, timeout time.Duration) (interface{}, error) { select { case data := <-ch: // 正常读取数据 return data, nil case <-time.After(timeout): // 超时触发 return nil, fmt.Errorf("read timeout after %v", timeout) } } // 调用示例 result, err := readWithTimeout(myChan, 500*time.Millisecond) if err != nil { log.Println("Error:", err) } else { log.Println("Data:", result) } |
问题5:Context应用(简略回答)
简述Context的主要用途,并实现一个基于Context的请求链路追踪系统。
协程间传递上下文消息,超时控制
每次请求ctx新建traceid,在传递过程中自动或者手动新增span(内含操作标识和消耗时间)
type Trace struct {
TraceID string
Spans []Span
}
func WithTrace(ctx context.Context) context.Context {
return context.WithValue(ctx, "trace", &Trace{TraceID: uuid.NewString()})
}
// 在HTTP请求头中传递TraceID实现跨服务追踪。
问题6:高并发锁的应用
在高并发场景下,锁的应用非常关键。请说明:
Go语言中sync.Mutex的锁升级过程,各种锁(sync.Mutex、sync.RWMutex、sync.Map)的适用场景和效率对比,死锁产生的条件和排查方法。
锁升级:Mutex从正常模式到饥饿模式的切换。
适用场景:
sync.Mutex:写多读少。
sync.RWMutex:读多写少(如缓存)。
sync.Map:读多写少且键值稳定(如配置)。
死锁条件:互斥、持有并等待、非抢占、循环等待。
排查:pprof或go tool trace分析阻塞情况。
死锁是指多个进程(或线程、协程)在竞争资源时,因互相持有对方所需的资源且不释放,导致所有参与者无限阻塞的状态。
死锁的四个必要条件
-
互斥条件:资源一次只能被一个进程占用(如锁、文件句柄)。
-
占有并等待:进程持有至少一个资源,同时等待其他被占用的资源。
-
非抢占条件:已分配给进程的资源不能被强制剥夺,只能主动释放。
-
循环等待:多个进程形成环形依赖链(如A等B,B等C,C等A)。
👉 只有这四个条件同时满足时,死锁才会发生。
第二部分:中间件使用
问题7:Redis高性能原理
Redis是单线程模型,为什么还能达到每秒数十万级的QPS?请解释其高性能的原因。
单线程:避免锁竞争和上下文切换。
内存存储:无磁盘IO瓶颈。
IO多路复用:epoll/kqueue高效处理海量连接。
第三部分:数据库使用
问题8:MySQL事务处理
编写代码实现一个银行转账操作,要求使用数据库事务保证数据一致性,并处理可能的死锁情况。
问题9:MySQL索引优化
分析以下SQL查询,设计合适的索引并解释优化原理:
SELECT user_id, order_date, status
FROM orders
WHERE user_id = 12345
AND status IN ('paid', 'shipped')
AND order_date > '2023-01-01'
ORDER BY order_date DESC
LIMIT 10;
建立一个联合索引依次命中,MySQL的查询优化器会自动优化WHERE条件的顺序,联合索引的命中与查询条件的书写顺序无关。此时联合索引同时是覆盖索引。
创建一个orde_date排序索引,显著提升速度
第四部分:问题排查与解决
问题10:性能问题排查
在生产环境中,你遇到过Go应用程序的内存泄漏或CPU使用率异常高的情况吗?请描述排查过程和使用的工具。
内存泄漏:
go tool pprof -http=:8080 http://localhost/debug/pprof/heap
检查goroutine泄漏或未释放的资源(如os.File)。
CPU过高:
top -H找到高占用线程,go tool pprof分析火焰图。
常见原因:死循环、频繁GC、阻塞调用(如锁竞争)。
建议:
重点补足:Channel超时、Context链路追踪、MySQL事务与锁。
工具熟练度:熟练使用pprof、EXPLAIN等工具。
代码实践:多写完整示例(如任务调度器)。
「三年博客,如果觉得我的文章对您有用,请帮助本站成长」
共有 0 - Golang工程师技术题